﻿using Abp.Collections.Extensions;
using Abp.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;

namespace Hicap.AuthorizationServer.Core.Common.EFCore
{
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity, ISoftDelete
    {
        protected readonly DbContext Context;

        public Repository(DbContext dbContext)
        {
            Context = dbContext;
            Context.EnsureAutoHistory();
        }

        protected virtual DbConnection Connection
        {
            get
            {
                DbConnection connection = Context.Database.GetDbConnection();

                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }

                return connection;
            }
        }

        protected virtual DbSet<TEntity> Table => Context.Set<TEntity>();

        public virtual int Count()
        {
            return GetQuery().Count();
        }

        public virtual int Count(Expression<Func<TEntity, bool>> predicate)
        {
            return GetQuery().Where(predicate).Count();
        }

        public async Task<int> CountAsync()
        {
            return await GetQuery().CountAsync();
        }

        public async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetQuery().Where(predicate).CountAsync();
        }

        public void Delete(TEntity entity, bool isSoftDelete = true)
        {
            AttachIfNot(entity);
            if (isSoftDelete)
            {
                entity.IsDeleted = true;
            }
            else
            {
                Table.Remove(entity);
            }
        }

        public void Delete(int id)
        {
            TEntity entity = GetFromChangeTrackerOrNull(id);
            if (entity != null)
            {
                Delete(entity);
                return;
            }

            entity = FirstOrDefault(id);
            if (entity != null)
            {
                Delete(entity);
                return;
            }
        }

        public virtual void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            foreach (TEntity entity in GetQuery().Where(predicate).ToList())
            {
                Delete(entity);
            }
        }

        public virtual Task DeleteAsync(TEntity entity)
        {
            Delete(entity);
            return Task.FromResult(0);
        }

        public virtual Task DeleteAsync(int id)
        {
            Delete(id);
            return Task.FromResult(0);
        }

        public virtual Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            Delete(predicate);
            return Task.FromResult(0);
        }

        public Task EnsureCollectionLoadedAsync<TProperty>(
            TEntity entity,
            Expression<Func<TEntity, IEnumerable<TProperty>>> collectionExpression,
            CancellationToken cancellationToken)
            where TProperty : class
        {
            return Context.Entry(entity).Collection(collectionExpression).LoadAsync(cancellationToken);
        }

        public Task EnsurePropertyLoadedAsync<TProperty>(
            TEntity entity,
            Expression<Func<TEntity, TProperty>> propertyExpression,
            CancellationToken cancellationToken)
            where TProperty : class
        {
            return Context.Entry(entity).Reference(propertyExpression).LoadAsync(cancellationToken);
        }

        public virtual TEntity FirstOrDefault(int id)
        {
            return GetQuery().FirstOrDefault(x => x.Id == id);
        }

        public async Task<TEntity> FirstOrDefaultAsync(int id)
        {
            return await GetQuery().FirstOrDefaultAsync(x => x.Id == id);
        }

        public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetQuery().FirstOrDefaultAsync(predicate);
        }

        public virtual TEntity Get(int id)
        {
            TEntity entity = FirstOrDefault(id);
            return entity;
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            return await GetQuery().ToListAsync();
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetQuery().Where(predicate).ToListAsync();
        }

        public virtual async Task<TEntity> GetAsync(int id)
        {
            TEntity entity = await FirstOrDefaultAsync(id);

            return entity;
        }

        public DbContext GetDbContext()
        {
            return Context;
        }

        public IQueryable<TEntity> GetIncludingQuery(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            IQueryable<TEntity> query = Table.AsQueryable();

            if (!propertySelectors.IsNullOrEmpty())
            {
                foreach (Expression<Func<TEntity, object>> propertySelector in propertySelectors)
                {
                    query = query.Include(propertySelector);
                }
            }

            return query;
        }

        public IQueryable<TEntity> GetQuery()
        {
            return GetIncludingQuery();
        }

        public TEntity Insert(TEntity entity)
        {
            return Table.Add(entity).Entity;
        }

        public int InsertAndGetId(TEntity entity)
        {
            entity = Insert(entity);

            if (entity.IsTransient())
            {
                Context.SaveChanges();
            }

            return entity.Id;
        }

        public async Task<int> InsertAndGetIdAsync(TEntity entity)
        {
            entity = await InsertAsync(entity);

            if (entity.IsTransient())
            {
                await Context.SaveChangesAsync();
            }

            return entity.Id;
        }

        public Task<TEntity> InsertAsync(TEntity entity)
        {
            return Task.FromResult(Insert(entity));
        }

        public virtual TEntity InsertOrUpdate(TEntity entity)
        {
            return entity.IsTransient()
                ? Insert(entity)
                : Update(entity);
        }

        public virtual int InsertOrUpdateAndGetId(TEntity entity)
        {
            entity = InsertOrUpdate(entity);

            if (entity.IsTransient())
            {
                Context.SaveChanges();
            }

            return entity.Id;
        }

        public async Task<int> InsertOrUpdateAndGetIdAsync(TEntity entity)
        {
            entity = await InsertOrUpdateAsync(entity);

            if (entity.IsTransient())
            {
                await Context.SaveChangesAsync();
            }

            return entity.Id;
        }

        public virtual async Task<TEntity> InsertOrUpdateAsync(TEntity entity)
        {
            return entity.IsTransient()
                ? await InsertAsync(entity)
                : await UpdateAsync(entity);
        }

        public virtual bool CheckHealth()
        {
            //            var result = Context.Set<TEntity>().Select(x => x.Id.ToString()).FromSql("select 9527");
            //            return result.Any();
            Context.Database.GetDbConnection().Open();
            bool result = Context.Database.GetDbConnection().State == ConnectionState.Open;
            Context.Database.GetDbConnection().Close();

            return result;
        }

        public virtual TEntity Load(int id)
        {
            return Get(id);
        }

        public virtual long LongCount()
        {
            return GetQuery().LongCount();
        }

        public virtual long LongCount(Expression<Func<TEntity, bool>> predicate)
        {
            return GetQuery().Where(predicate).LongCount();
        }

        public virtual async Task<long> LongCountAsync()
        {
            return await GetQuery().LongCountAsync();
        }

        public virtual async Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetQuery().Where(predicate).LongCountAsync();
        }

        public virtual async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            return await GetQuery().SingleAsync(predicate);
        }

        public virtual TEntity Update(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Entry(entity).State = EntityState.Modified;
            return entity;
        }

        public virtual TEntity Update(int id, Action<TEntity> updateAction)
        {
            TEntity entity = Get(id);
            updateAction(entity);
            return entity;
        }

        public virtual Task<TEntity> UpdateAsync(TEntity entity)
        {
            entity = Update(entity);
            return Task.FromResult(entity);
        }

        public virtual async Task<TEntity> UpdateAsync(int id, Func<TEntity, Task> updateAction)
        {
            TEntity entity = await GetAsync(id);
            await updateAction(entity);
            return entity;
        }

        public virtual async Task BatchInsertAsync(IList<TEntity> list)
        {
            await Context.AddRangeAsync(list);
        }

        public virtual Task BatchUpdateAsync(IList<TEntity> list)
        {
            return Task.Run(() => Context.UpdateRange(list));
        }

        public virtual async Task BatchInsertOrUpdateAsync(IList<TEntity> list)
        {
            List<TEntity> insertList = new List<TEntity>();
            List<TEntity> updateList = new List<TEntity>();
            foreach (TEntity entity in list)
            {
                if (entity.IsTransient()) insertList.Add(entity);
                else updateList.Add(entity);
            }
            if (insertList.Count > 0) await BatchInsertAsync(list);
            if (updateList.Count > 0) await BatchUpdateAsync(list);
        }

        protected virtual void AttachIfNot(TEntity entity)
        {
            Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entry = Context.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
            if (entry != null)
            {
                return;
            }

            Table.Attach(entity);
        }

        private TEntity GetFromChangeTrackerOrNull(int id)
        {
            Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry entry = Context.ChangeTracker.Entries()
                .FirstOrDefault(
                    ent =>
                        ent.Entity is TEntity &&
                        EqualityComparer<int>.Default.Equals(id, (ent.Entity as TEntity).Id)
                );

            return entry?.Entity as TEntity;
        }
    }
}